/*
 * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */
package java.util.stream;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.PrimitiveIterator;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;
import java.util.function.DoubleConsumer;
import java.util.function.IntConsumer;
import java.util.function.IntFunction;
import java.util.function.LongConsumer;

/**
 * An ordered collection of elements.  Elements can be added, but not removed.
 * Goes through a building phase, during which elements can be added, and a
 * traversal phase, during which elements can be traversed in order but no
 * further modifications are possible.
 *
 * <p> One or more arrays are used to store elements. The use of a multiple
 * arrays has better performance characteristics than a single array used by
 * {@link ArrayList}, as when the capacity of the list needs to be increased
 * no copying of elements is required.  This is usually beneficial in the case
 * where the results will be traversed a small number of times.
 *
 * @param <E> the type of elements in this list
 * @since 1.8
 */
class SpinedBuffer<E>
    extends AbstractSpinedBuffer
    implements Consumer<E>, Iterable<E> {

    /*
     * We optimistically hope that all the data will fit into the first chunk,
     * so we try to avoid inflating the spine[] and priorElementCount[] arrays
     * prematurely.  So methods must be prepared to deal with these arrays being
     * null.  If spine is non-null, then spineIndex points to the current chunk
     * within the spine, otherwise it is zero.  The spine and priorElementCount
     * arrays are always the same size, and for any i <= spineIndex,
     * priorElementCount[i] is the sum of the sizes of all the prior chunks.
     *
     * The curChunk pointer is always valid.  The elementIndex is the index of
     * the next element to be written in curChunk; this may be past the end of
     * curChunk so we have to check before writing. When we inflate the spine
     * array, curChunk becomes the first element in it.  When we clear the
     * buffer, we discard all chunks except the first one, which we clear,
     * restoring it to the initial single-chunk state.
     */

  /**
   * Chunk that we're currently writing into; may or may not be aliased with
   * the first element of the spine.
   */
  protected E[] curChunk;

  /**
   * All chunks, or null if there is only one chunk.
   */
  protected E[][] spine;

  /**
   * Constructs an empty list with the specified initial capacity.
   *
   * @param initialCapacity the initial capacity of the list
   * @throws IllegalArgumentException if the specified initial capacity is negative
   */
  @SuppressWarnings("unchecked")
  SpinedBuffer(int initialCapacity) {
    super(initialCapacity);
    curChunk = (E[]) new Object[1 << initialChunkPower];
  }

  /**
   * Constructs an empty list with an initial capacity of sixteen.
   */
  @SuppressWarnings("unchecked")
  SpinedBuffer() {
    super();
    curChunk = (E[]) new Object[1 << initialChunkPower];
  }

  /**
   * Returns the current capacity of the buffer
   */
  protected long capacity() {
    return (spineIndex == 0)
        ? curChunk.length
        : priorElementCount[spineIndex] + spine[spineIndex].length;
  }

  @SuppressWarnings("unchecked")
  private void inflateSpine() {
    if (spine == null) {
      spine = (E[][]) new Object[MIN_SPINE_SIZE][];
      priorElementCount = new long[MIN_SPINE_SIZE];
      spine[0] = curChunk;
    }
  }

  /**
   * Ensure that the buffer has at least capacity to hold the target size
   */
  @SuppressWarnings("unchecked")
  protected final void ensureCapacity(long targetSize) {
    long capacity = capacity();
    if (targetSize > capacity) {
      inflateSpine();
      for (int i = spineIndex + 1; targetSize > capacity; i++) {
        if (i >= spine.length) {
          int newSpineSize = spine.length * 2;
          spine = Arrays.copyOf(spine, newSpineSize);
          priorElementCount = Arrays.copyOf(priorElementCount, newSpineSize);
        }
        int nextChunkSize = chunkSize(i);
        spine[i] = (E[]) new Object[nextChunkSize];
        priorElementCount[i] = priorElementCount[i - 1] + spine[i - 1].length;
        capacity += nextChunkSize;
      }
    }
  }

  /**
   * Force the buffer to increase its capacity.
   */
  protected void increaseCapacity() {
    ensureCapacity(capacity() + 1);
  }

  /**
   * Retrieve the element at the specified index.
   */
  public E get(long index) {
    // @@@ can further optimize by caching last seen spineIndex,
    // which is going to be right most of the time

    // Casts to int are safe since the spine array index is the index minus
    // the prior element count from the current spine
    if (spineIndex == 0) {
      if (index < elementIndex) {
        return curChunk[((int) index)];
      } else {
        throw new IndexOutOfBoundsException(Long.toString(index));
      }
    }

    if (index >= count()) {
      throw new IndexOutOfBoundsException(Long.toString(index));
    }

    for (int j = 0; j <= spineIndex; j++) {
      if (index < priorElementCount[j] + spine[j].length) {
        return spine[j][((int) (index - priorElementCount[j]))];
      }
    }

    throw new IndexOutOfBoundsException(Long.toString(index));
  }

  /**
   * Copy the elements, starting at the specified offset, into the specified
   * array.
   */
  public void copyInto(E[] array, int offset) {
    long finalOffset = offset + count();
    if (finalOffset > array.length || finalOffset < offset) {
      throw new IndexOutOfBoundsException("does not fit");
    }

    if (spineIndex == 0) {
      System.arraycopy(curChunk, 0, array, offset, elementIndex);
    } else {
      // full chunks
      for (int i = 0; i < spineIndex; i++) {
        System.arraycopy(spine[i], 0, array, offset, spine[i].length);
        offset += spine[i].length;
      }
      if (elementIndex > 0) {
        System.arraycopy(curChunk, 0, array, offset, elementIndex);
      }
    }
  }

  /**
   * Create a new array using the specified array factory, and copy the
   * elements into it.
   */
  public E[] asArray(IntFunction<E[]> arrayFactory) {
    long size = count();
    if (size >= Nodes.MAX_ARRAY_SIZE) {
      throw new IllegalArgumentException(Nodes.BAD_SIZE);
    }
    E[] result = arrayFactory.apply((int) size);
    copyInto(result, 0);
    return result;
  }

  @Override
  public void clear() {
    if (spine != null) {
      curChunk = spine[0];
      for (int i = 0; i < curChunk.length; i++) {
        curChunk[i] = null;
      }
      spine = null;
      priorElementCount = null;
    } else {
      for (int i = 0; i < elementIndex; i++) {
        curChunk[i] = null;
      }
    }
    elementIndex = 0;
    spineIndex = 0;
  }

  @Override
  public Iterator<E> iterator() {
    return Spliterators.iterator(spliterator());
  }

  @Override
  public void forEach(Consumer<? super E> consumer) {
    // completed chunks, if any
    for (int j = 0; j < spineIndex; j++) {
      for (E t : spine[j]) {
        consumer.accept(t);
      }
    }

    // current chunk
    for (int i = 0; i < elementIndex; i++) {
      consumer.accept(curChunk[i]);
    }
  }

  @Override
  public void accept(E e) {
    if (elementIndex == curChunk.length) {
      inflateSpine();
      if (spineIndex + 1 >= spine.length || spine[spineIndex + 1] == null) {
        increaseCapacity();
      }
      elementIndex = 0;
      ++spineIndex;
      curChunk = spine[spineIndex];
    }
    curChunk[elementIndex++] = e;
  }

  @Override
  public String toString() {
    List<E> list = new ArrayList<>();
    forEach(list::add);
    return "SpinedBuffer:" + list.toString();
  }

  private static final int SPLITERATOR_CHARACTERISTICS
      = Spliterator.SIZED | Spliterator.ORDERED | Spliterator.SUBSIZED;

  /**
   * Return a {@link Spliterator} describing the contents of the buffer.
   */
  public Spliterator<E> spliterator() {
    class Splitr implements Spliterator<E> {

      // The current spine index
      int splSpineIndex;

      // Last spine index
      final int lastSpineIndex;

      // The current element index into the current spine
      int splElementIndex;

      // Last spine's last element index + 1
      final int lastSpineElementFence;

      // When splSpineIndex >= lastSpineIndex and
      // splElementIndex >= lastSpineElementFence then
      // this spliterator is fully traversed
      // tryAdvance can set splSpineIndex > spineIndex if the last spine is full

      // The current spine array
      E[] splChunk;

      Splitr(int firstSpineIndex, int lastSpineIndex,
          int firstSpineElementIndex, int lastSpineElementFence) {
        this.splSpineIndex = firstSpineIndex;
        this.lastSpineIndex = lastSpineIndex;
        this.splElementIndex = firstSpineElementIndex;
        this.lastSpineElementFence = lastSpineElementFence;
        assert spine != null || firstSpineIndex == 0 && lastSpineIndex == 0;
        splChunk = (spine == null) ? curChunk : spine[firstSpineIndex];
      }

      @Override
      public long estimateSize() {
        return (splSpineIndex == lastSpineIndex)
            ? (long) lastSpineElementFence - splElementIndex
            : // # of elements prior to end -
                priorElementCount[lastSpineIndex] + lastSpineElementFence -
                    // # of elements prior to current
                    priorElementCount[splSpineIndex] - splElementIndex;
      }

      @Override
      public int characteristics() {
        return SPLITERATOR_CHARACTERISTICS;
      }

      @Override
      public boolean tryAdvance(Consumer<? super E> consumer) {
        Objects.requireNonNull(consumer);

        if (splSpineIndex < lastSpineIndex
            || (splSpineIndex == lastSpineIndex && splElementIndex < lastSpineElementFence)) {
          consumer.accept(splChunk[splElementIndex++]);

          if (splElementIndex == splChunk.length) {
            splElementIndex = 0;
            ++splSpineIndex;
            if (spine != null && splSpineIndex <= lastSpineIndex) {
              splChunk = spine[splSpineIndex];
            }
          }
          return true;
        }
        return false;
      }

      @Override
      public void forEachRemaining(Consumer<? super E> consumer) {
        Objects.requireNonNull(consumer);

        if (splSpineIndex < lastSpineIndex
            || (splSpineIndex == lastSpineIndex && splElementIndex < lastSpineElementFence)) {
          int i = splElementIndex;
          // completed chunks, if any
          for (int sp = splSpineIndex; sp < lastSpineIndex; sp++) {
            E[] chunk = spine[sp];
            for (; i < chunk.length; i++) {
              consumer.accept(chunk[i]);
            }
            i = 0;
          }
          // last (or current uncompleted) chunk
          E[] chunk = (splSpineIndex == lastSpineIndex) ? splChunk : spine[lastSpineIndex];
          int hElementIndex = lastSpineElementFence;
          for (; i < hElementIndex; i++) {
            consumer.accept(chunk[i]);
          }
          // mark consumed
          splSpineIndex = lastSpineIndex;
          splElementIndex = lastSpineElementFence;
        }
      }

      @Override
      public Spliterator<E> trySplit() {
        if (splSpineIndex < lastSpineIndex) {
          // split just before last chunk (if it is full this means 50:50 split)
          Spliterator<E> ret = new Splitr(splSpineIndex, lastSpineIndex - 1,
              splElementIndex, spine[lastSpineIndex - 1].length);
          // position to start of last chunk
          splSpineIndex = lastSpineIndex;
          splElementIndex = 0;
          splChunk = spine[splSpineIndex];
          return ret;
        } else if (splSpineIndex == lastSpineIndex) {
          int t = (lastSpineElementFence - splElementIndex) / 2;
          if (t == 0) {
            return null;
          } else {
            Spliterator<E> ret = Arrays.spliterator(splChunk, splElementIndex, splElementIndex + t);
            splElementIndex += t;
            return ret;
          }
        } else {
          return null;
        }
      }
    }
    return new Splitr(0, spineIndex, 0, elementIndex);
  }

  /**
   * An ordered collection of primitive values.  Elements can be added, but
   * not removed. Goes through a building phase, during which elements can be
   * added, and a traversal phase, during which elements can be traversed in
   * order but no further modifications are possible.
   *
   * <p> One or more arrays are used to store elements. The use of a multiple
   * arrays has better performance characteristics than a single array used by
   * {@link ArrayList}, as when the capacity of the list needs to be increased
   * no copying of elements is required.  This is usually beneficial in the case
   * where the results will be traversed a small number of times.
   *
   * @param <E> the wrapper type for this primitive type
   * @param <T_ARR> the array type for this primitive type
   * @param <T_CONS> the Consumer type for this primitive type
   */
  abstract static class OfPrimitive<E, T_ARR, T_CONS>
      extends AbstractSpinedBuffer implements Iterable<E> {

        /*
         * We optimistically hope that all the data will fit into the first chunk,
         * so we try to avoid inflating the spine[] and priorElementCount[] arrays
         * prematurely.  So methods must be prepared to deal with these arrays being
         * null.  If spine is non-null, then spineIndex points to the current chunk
         * within the spine, otherwise it is zero.  The spine and priorElementCount
         * arrays are always the same size, and for any i <= spineIndex,
         * priorElementCount[i] is the sum of the sizes of all the prior chunks.
         *
         * The curChunk pointer is always valid.  The elementIndex is the index of
         * the next element to be written in curChunk; this may be past the end of
         * curChunk so we have to check before writing. When we inflate the spine
         * array, curChunk becomes the first element in it.  When we clear the
         * buffer, we discard all chunks except the first one, which we clear,
         * restoring it to the initial single-chunk state.
         */

    // The chunk we're currently writing into
    T_ARR curChunk;

    // All chunks, or null if there is only one chunk
    T_ARR[] spine;

    /**
     * Constructs an empty list with the specified initial capacity.
     *
     * @param initialCapacity the initial capacity of the list
     * @throws IllegalArgumentException if the specified initial capacity is negative
     */
    OfPrimitive(int initialCapacity) {
      super(initialCapacity);
      curChunk = newArray(1 << initialChunkPower);
    }

    /**
     * Constructs an empty list with an initial capacity of sixteen.
     */
    OfPrimitive() {
      super();
      curChunk = newArray(1 << initialChunkPower);
    }

    @Override
    public abstract Iterator<E> iterator();

    @Override
    public abstract void forEach(Consumer<? super E> consumer);

    /**
     * Create a new array-of-array of the proper type and size
     */
    protected abstract T_ARR[] newArrayArray(int size);

    /**
     * Create a new array of the proper type and size
     */
    public abstract T_ARR newArray(int size);

    /**
     * Get the length of an array
     */
    protected abstract int arrayLength(T_ARR array);

    /**
     * Iterate an array with the provided consumer
     */
    protected abstract void arrayForEach(T_ARR array, int from, int to,
        T_CONS consumer);

    protected long capacity() {
      return (spineIndex == 0)
          ? arrayLength(curChunk)
          : priorElementCount[spineIndex] + arrayLength(spine[spineIndex]);
    }

    private void inflateSpine() {
      if (spine == null) {
        spine = newArrayArray(MIN_SPINE_SIZE);
        priorElementCount = new long[MIN_SPINE_SIZE];
        spine[0] = curChunk;
      }
    }

    protected final void ensureCapacity(long targetSize) {
      long capacity = capacity();
      if (targetSize > capacity) {
        inflateSpine();
        for (int i = spineIndex + 1; targetSize > capacity; i++) {
          if (i >= spine.length) {
            int newSpineSize = spine.length * 2;
            spine = Arrays.copyOf(spine, newSpineSize);
            priorElementCount = Arrays.copyOf(priorElementCount, newSpineSize);
          }
          int nextChunkSize = chunkSize(i);
          spine[i] = newArray(nextChunkSize);
          priorElementCount[i] = priorElementCount[i - 1] + arrayLength(spine[i - 1]);
          capacity += nextChunkSize;
        }
      }
    }

    protected void increaseCapacity() {
      ensureCapacity(capacity() + 1);
    }

    protected int chunkFor(long index) {
      if (spineIndex == 0) {
        if (index < elementIndex) {
          return 0;
        } else {
          throw new IndexOutOfBoundsException(Long.toString(index));
        }
      }

      if (index >= count()) {
        throw new IndexOutOfBoundsException(Long.toString(index));
      }

      for (int j = 0; j <= spineIndex; j++) {
        if (index < priorElementCount[j] + arrayLength(spine[j])) {
          return j;
        }
      }

      throw new IndexOutOfBoundsException(Long.toString(index));
    }

    public void copyInto(T_ARR array, int offset) {
      long finalOffset = offset + count();
      if (finalOffset > arrayLength(array) || finalOffset < offset) {
        throw new IndexOutOfBoundsException("does not fit");
      }

      if (spineIndex == 0) {
        System.arraycopy(curChunk, 0, array, offset, elementIndex);
      } else {
        // full chunks
        for (int i = 0; i < spineIndex; i++) {
          System.arraycopy(spine[i], 0, array, offset, arrayLength(spine[i]));
          offset += arrayLength(spine[i]);
        }
        if (elementIndex > 0) {
          System.arraycopy(curChunk, 0, array, offset, elementIndex);
        }
      }
    }

    public T_ARR asPrimitiveArray() {
      long size = count();
      if (size >= Nodes.MAX_ARRAY_SIZE) {
        throw new IllegalArgumentException(Nodes.BAD_SIZE);
      }
      T_ARR result = newArray((int) size);
      copyInto(result, 0);
      return result;
    }

    protected void preAccept() {
      if (elementIndex == arrayLength(curChunk)) {
        inflateSpine();
        if (spineIndex + 1 >= spine.length || spine[spineIndex + 1] == null) {
          increaseCapacity();
        }
        elementIndex = 0;
        ++spineIndex;
        curChunk = spine[spineIndex];
      }
    }

    public void clear() {
      if (spine != null) {
        curChunk = spine[0];
        spine = null;
        priorElementCount = null;
      }
      elementIndex = 0;
      spineIndex = 0;
    }

    @SuppressWarnings("overloads")
    public void forEach(T_CONS consumer) {
      // completed chunks, if any
      for (int j = 0; j < spineIndex; j++) {
        arrayForEach(spine[j], 0, arrayLength(spine[j]), consumer);
      }

      // current chunk
      arrayForEach(curChunk, 0, elementIndex, consumer);
    }

    abstract class BaseSpliterator<T_SPLITR extends Spliterator.OfPrimitive<E, T_CONS, T_SPLITR>>
        implements Spliterator.OfPrimitive<E, T_CONS, T_SPLITR> {

      // The current spine index
      int splSpineIndex;

      // Last spine index
      final int lastSpineIndex;

      // The current element index into the current spine
      int splElementIndex;

      // Last spine's last element index + 1
      final int lastSpineElementFence;

      // When splSpineIndex >= lastSpineIndex and
      // splElementIndex >= lastSpineElementFence then
      // this spliterator is fully traversed
      // tryAdvance can set splSpineIndex > spineIndex if the last spine is full

      // The current spine array
      T_ARR splChunk;

      BaseSpliterator(int firstSpineIndex, int lastSpineIndex,
          int firstSpineElementIndex, int lastSpineElementFence) {
        this.splSpineIndex = firstSpineIndex;
        this.lastSpineIndex = lastSpineIndex;
        this.splElementIndex = firstSpineElementIndex;
        this.lastSpineElementFence = lastSpineElementFence;
        assert spine != null || firstSpineIndex == 0 && lastSpineIndex == 0;
        splChunk = (spine == null) ? curChunk : spine[firstSpineIndex];
      }

      abstract T_SPLITR newSpliterator(int firstSpineIndex, int lastSpineIndex,
          int firstSpineElementIndex, int lastSpineElementFence);

      abstract void arrayForOne(T_ARR array, int index, T_CONS consumer);

      abstract T_SPLITR arraySpliterator(T_ARR array, int offset, int len);

      @Override
      public long estimateSize() {
        return (splSpineIndex == lastSpineIndex)
            ? (long) lastSpineElementFence - splElementIndex
            : // # of elements prior to end -
                priorElementCount[lastSpineIndex] + lastSpineElementFence -
                    // # of elements prior to current
                    priorElementCount[splSpineIndex] - splElementIndex;
      }

      @Override
      public int characteristics() {
        return SPLITERATOR_CHARACTERISTICS;
      }

      @Override
      public boolean tryAdvance(T_CONS consumer) {
        Objects.requireNonNull(consumer);

        if (splSpineIndex < lastSpineIndex
            || (splSpineIndex == lastSpineIndex && splElementIndex < lastSpineElementFence)) {
          arrayForOne(splChunk, splElementIndex++, consumer);

          if (splElementIndex == arrayLength(splChunk)) {
            splElementIndex = 0;
            ++splSpineIndex;
            if (spine != null && splSpineIndex <= lastSpineIndex) {
              splChunk = spine[splSpineIndex];
            }
          }
          return true;
        }
        return false;
      }

      @Override
      public void forEachRemaining(T_CONS consumer) {
        Objects.requireNonNull(consumer);

        if (splSpineIndex < lastSpineIndex
            || (splSpineIndex == lastSpineIndex && splElementIndex < lastSpineElementFence)) {
          int i = splElementIndex;
          // completed chunks, if any
          for (int sp = splSpineIndex; sp < lastSpineIndex; sp++) {
            T_ARR chunk = spine[sp];
            arrayForEach(chunk, i, arrayLength(chunk), consumer);
            i = 0;
          }
          // last (or current uncompleted) chunk
          T_ARR chunk = (splSpineIndex == lastSpineIndex) ? splChunk : spine[lastSpineIndex];
          arrayForEach(chunk, i, lastSpineElementFence, consumer);
          // mark consumed
          splSpineIndex = lastSpineIndex;
          splElementIndex = lastSpineElementFence;
        }
      }

      @Override
      public T_SPLITR trySplit() {
        if (splSpineIndex < lastSpineIndex) {
          // split just before last chunk (if it is full this means 50:50 split)
          T_SPLITR ret = newSpliterator(splSpineIndex, lastSpineIndex - 1,
              splElementIndex, arrayLength(spine[lastSpineIndex - 1]));
          // position us to start of last chunk
          splSpineIndex = lastSpineIndex;
          splElementIndex = 0;
          splChunk = spine[splSpineIndex];
          return ret;
        } else if (splSpineIndex == lastSpineIndex) {
          int t = (lastSpineElementFence - splElementIndex) / 2;
          if (t == 0) {
            return null;
          } else {
            T_SPLITR ret = arraySpliterator(splChunk, splElementIndex, t);
            splElementIndex += t;
            return ret;
          }
        } else {
          return null;
        }
      }
    }
  }

  /**
   * An ordered collection of {@code int} values.
   */
  static class OfInt extends SpinedBuffer.OfPrimitive<Integer, int[], IntConsumer>
      implements IntConsumer {

    OfInt() {
    }

    OfInt(int initialCapacity) {
      super(initialCapacity);
    }

    @Override
    public void forEach(Consumer<? super Integer> consumer) {
      if (consumer instanceof IntConsumer) {
        forEach((IntConsumer) consumer);
      } else {
        if (Tripwire.ENABLED) {
          Tripwire.trip(getClass(), "{0} calling SpinedBuffer.OfInt.forEach(Consumer)");
        }
        spliterator().forEachRemaining(consumer);
      }
    }

    @Override
    protected int[][] newArrayArray(int size) {
      return new int[size][];
    }

    @Override
    public int[] newArray(int size) {
      return new int[size];
    }

    @Override
    protected int arrayLength(int[] array) {
      return array.length;
    }

    @Override
    protected void arrayForEach(int[] array,
        int from, int to,
        IntConsumer consumer) {
      for (int i = from; i < to; i++) {
        consumer.accept(array[i]);
      }
    }

    @Override
    public void accept(int i) {
      preAccept();
      curChunk[elementIndex++] = i;
    }

    public int get(long index) {
      // Casts to int are safe since the spine array index is the index minus
      // the prior element count from the current spine
      int ch = chunkFor(index);
      if (spineIndex == 0 && ch == 0) {
        return curChunk[(int) index];
      } else {
        return spine[ch][(int) (index - priorElementCount[ch])];
      }
    }

    @Override
    public PrimitiveIterator.OfInt iterator() {
      return Spliterators.iterator(spliterator());
    }

    public Spliterator.OfInt spliterator() {
      class Splitr extends BaseSpliterator<Spliterator.OfInt>
          implements Spliterator.OfInt {

        Splitr(int firstSpineIndex, int lastSpineIndex,
            int firstSpineElementIndex, int lastSpineElementFence) {
          super(firstSpineIndex, lastSpineIndex,
              firstSpineElementIndex, lastSpineElementFence);
        }

        @Override
        Splitr newSpliterator(int firstSpineIndex, int lastSpineIndex,
            int firstSpineElementIndex, int lastSpineElementFence) {
          return new Splitr(firstSpineIndex, lastSpineIndex,
              firstSpineElementIndex, lastSpineElementFence);
        }

        @Override
        void arrayForOne(int[] array, int index, IntConsumer consumer) {
          consumer.accept(array[index]);
        }

        @Override
        Spliterator.OfInt arraySpliterator(int[] array, int offset, int len) {
          return Arrays.spliterator(array, offset, offset + len);
        }
      }
      return new Splitr(0, spineIndex, 0, elementIndex);
    }

    @Override
    public String toString() {
      int[] array = asPrimitiveArray();
      if (array.length < 200) {
        return String.format("%s[length=%d, chunks=%d]%s",
            getClass().getSimpleName(), array.length,
            spineIndex, Arrays.toString(array));
      } else {
        int[] array2 = Arrays.copyOf(array, 200);
        return String.format("%s[length=%d, chunks=%d]%s...",
            getClass().getSimpleName(), array.length,
            spineIndex, Arrays.toString(array2));
      }
    }
  }

  /**
   * An ordered collection of {@code long} values.
   */
  static class OfLong extends SpinedBuffer.OfPrimitive<Long, long[], LongConsumer>
      implements LongConsumer {

    OfLong() {
    }

    OfLong(int initialCapacity) {
      super(initialCapacity);
    }

    @Override
    public void forEach(Consumer<? super Long> consumer) {
      if (consumer instanceof LongConsumer) {
        forEach((LongConsumer) consumer);
      } else {
        if (Tripwire.ENABLED) {
          Tripwire.trip(getClass(), "{0} calling SpinedBuffer.OfLong.forEach(Consumer)");
        }
        spliterator().forEachRemaining(consumer);
      }
    }

    @Override
    protected long[][] newArrayArray(int size) {
      return new long[size][];
    }

    @Override
    public long[] newArray(int size) {
      return new long[size];
    }

    @Override
    protected int arrayLength(long[] array) {
      return array.length;
    }

    @Override
    protected void arrayForEach(long[] array,
        int from, int to,
        LongConsumer consumer) {
      for (int i = from; i < to; i++) {
        consumer.accept(array[i]);
      }
    }

    @Override
    public void accept(long i) {
      preAccept();
      curChunk[elementIndex++] = i;
    }

    public long get(long index) {
      // Casts to int are safe since the spine array index is the index minus
      // the prior element count from the current spine
      int ch = chunkFor(index);
      if (spineIndex == 0 && ch == 0) {
        return curChunk[(int) index];
      } else {
        return spine[ch][(int) (index - priorElementCount[ch])];
      }
    }

    @Override
    public PrimitiveIterator.OfLong iterator() {
      return Spliterators.iterator(spliterator());
    }


    public Spliterator.OfLong spliterator() {
      class Splitr extends BaseSpliterator<Spliterator.OfLong>
          implements Spliterator.OfLong {

        Splitr(int firstSpineIndex, int lastSpineIndex,
            int firstSpineElementIndex, int lastSpineElementFence) {
          super(firstSpineIndex, lastSpineIndex,
              firstSpineElementIndex, lastSpineElementFence);
        }

        @Override
        Splitr newSpliterator(int firstSpineIndex, int lastSpineIndex,
            int firstSpineElementIndex, int lastSpineElementFence) {
          return new Splitr(firstSpineIndex, lastSpineIndex,
              firstSpineElementIndex, lastSpineElementFence);
        }

        @Override
        void arrayForOne(long[] array, int index, LongConsumer consumer) {
          consumer.accept(array[index]);
        }

        @Override
        Spliterator.OfLong arraySpliterator(long[] array, int offset, int len) {
          return Arrays.spliterator(array, offset, offset + len);
        }
      }
      return new Splitr(0, spineIndex, 0, elementIndex);
    }

    @Override
    public String toString() {
      long[] array = asPrimitiveArray();
      if (array.length < 200) {
        return String.format("%s[length=%d, chunks=%d]%s",
            getClass().getSimpleName(), array.length,
            spineIndex, Arrays.toString(array));
      } else {
        long[] array2 = Arrays.copyOf(array, 200);
        return String.format("%s[length=%d, chunks=%d]%s...",
            getClass().getSimpleName(), array.length,
            spineIndex, Arrays.toString(array2));
      }
    }
  }

  /**
   * An ordered collection of {@code double} values.
   */
  static class OfDouble
      extends SpinedBuffer.OfPrimitive<Double, double[], DoubleConsumer>
      implements DoubleConsumer {

    OfDouble() {
    }

    OfDouble(int initialCapacity) {
      super(initialCapacity);
    }

    @Override
    public void forEach(Consumer<? super Double> consumer) {
      if (consumer instanceof DoubleConsumer) {
        forEach((DoubleConsumer) consumer);
      } else {
        if (Tripwire.ENABLED) {
          Tripwire.trip(getClass(), "{0} calling SpinedBuffer.OfDouble.forEach(Consumer)");
        }
        spliterator().forEachRemaining(consumer);
      }
    }

    @Override
    protected double[][] newArrayArray(int size) {
      return new double[size][];
    }

    @Override
    public double[] newArray(int size) {
      return new double[size];
    }

    @Override
    protected int arrayLength(double[] array) {
      return array.length;
    }

    @Override
    protected void arrayForEach(double[] array,
        int from, int to,
        DoubleConsumer consumer) {
      for (int i = from; i < to; i++) {
        consumer.accept(array[i]);
      }
    }

    @Override
    public void accept(double i) {
      preAccept();
      curChunk[elementIndex++] = i;
    }

    public double get(long index) {
      // Casts to int are safe since the spine array index is the index minus
      // the prior element count from the current spine
      int ch = chunkFor(index);
      if (spineIndex == 0 && ch == 0) {
        return curChunk[(int) index];
      } else {
        return spine[ch][(int) (index - priorElementCount[ch])];
      }
    }

    @Override
    public PrimitiveIterator.OfDouble iterator() {
      return Spliterators.iterator(spliterator());
    }

    public Spliterator.OfDouble spliterator() {
      class Splitr extends BaseSpliterator<Spliterator.OfDouble>
          implements Spliterator.OfDouble {

        Splitr(int firstSpineIndex, int lastSpineIndex,
            int firstSpineElementIndex, int lastSpineElementFence) {
          super(firstSpineIndex, lastSpineIndex,
              firstSpineElementIndex, lastSpineElementFence);
        }

        @Override
        Splitr newSpliterator(int firstSpineIndex, int lastSpineIndex,
            int firstSpineElementIndex, int lastSpineElementFence) {
          return new Splitr(firstSpineIndex, lastSpineIndex,
              firstSpineElementIndex, lastSpineElementFence);
        }

        @Override
        void arrayForOne(double[] array, int index, DoubleConsumer consumer) {
          consumer.accept(array[index]);
        }

        @Override
        Spliterator.OfDouble arraySpliterator(double[] array, int offset, int len) {
          return Arrays.spliterator(array, offset, offset + len);
        }
      }
      return new Splitr(0, spineIndex, 0, elementIndex);
    }

    @Override
    public String toString() {
      double[] array = asPrimitiveArray();
      if (array.length < 200) {
        return String.format("%s[length=%d, chunks=%d]%s",
            getClass().getSimpleName(), array.length,
            spineIndex, Arrays.toString(array));
      } else {
        double[] array2 = Arrays.copyOf(array, 200);
        return String.format("%s[length=%d, chunks=%d]%s...",
            getClass().getSimpleName(), array.length,
            spineIndex, Arrays.toString(array2));
      }
    }
  }
}

